﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Collections.Generic;
using System.Text;

using Infrastructure.Diagnostics;
using TempEmployees.DAL;
using TempEmployees.BLL.Database;
using TempEmployees.BLL.Forms;
using TempEmployees.BLL.Validations;

namespace TempEmployees.BLL.EntitiesExtensions
{
    public static class EAssignment
    {
        #region static methods

        public static Assignment CreateAssignment(int budgetTypeId, string budgetNumber, decimal salary, int? semesterId, 
            int monthlyHours, DateTime startDate, DateTime endDate, string userId, string employeeId, int? prevAssignment = null)
        {
            Assignment assignment = new Assignment();
            assignment.UserID = userId;
            try
            {
                assignment.DepartmentID =
                    DbQuery.GetSingleRow<UserDepartment>("UserID", userId).DepartmentID;
            }
            catch { }
            assignment.EmployeeID = employeeId;
            assignment.SetBudgetNumber(budgetNumber, budgetTypeId);
            assignment.StartDate = startDate;
            assignment.EndDate = endDate;
            assignment.MonthlyHours = monthlyHours;
            assignment.Salary = salary;
            assignment.SemesterID = semesterId;
            assignment.StatusID = (int)DbConstants.Status.Pending;
            assignment.PrevAssignmentID = prevAssignment;
            return assignment;
        }

        public static bool UpdateAssignment(Assignment toUpdate, out Assignment updatedAssignment)
        {
            updatedAssignment = null;
            if (toUpdate.ID < 1) throw new RecordNotInDbException("UpdateAssignment: assignment not found in the DB");
            if (!toUpdate.CheckIfCanUpdate()) return false;

            try
            {
                DbConstants.Status currStatus = (DbConstants.Status)toUpdate.StatusID;
                Logger.Debug("Updating assignment. id = {0} , status = {1}", toUpdate.ID, currStatus);
                switch (currStatus)
                {
                    case DbConstants.Status.Active: //Active status dealt as Approved
                    case DbConstants.Status.Approved:
                        {
                            updatedAssignment = EAssignment.CreateAssignment(toUpdate.Budget.TypeID, toUpdate.BudgetID,
                            toUpdate.Salary, toUpdate.SemesterID, toUpdate.MonthlyHours, toUpdate.StartDate, toUpdate.EndDate,
                            toUpdate.UserID, toUpdate.EmployeeID, toUpdate.ID);
                            return updatedAssignment.Insert();
                        }
                    case DbConstants.Status.Rejected: //if a rejected assignment is updated, it will turn to Pending status again.
                        {
                            toUpdate.StatusID = (int)DbConstants.Status.Pending;
                            goto case DbConstants.Status.Pending;
                        }
                    case DbConstants.Status.Pending:
                        {
                            if (toUpdate.DoUpdate())
                            {
                                updatedAssignment = toUpdate;
                                return true;
                            }
                            break;
                        }
                }
                
                return false;
            }
            catch(ValidationErrorException)
            {
                return false;
            } 
        }

        public static Assignment GetAssignmentById(int id)
        {
            try
            {
                return DbQuery.SelectByPK<Assignment>(id.ToString());
            }
            catch
            {
                return null;
            }
        }

        public static bool InsertComment(int assignmentId, string userId, string comment)
        {
            Note note = new Note();
            note.Text = comment;
            note.AssignmentID = assignmentId;
            note.UserID = userId;
            note.Date = DateTime.Now;
            if (!DbQuery.Insert<Note>(note)) return false;
            return true;
        }

        public static string GetNotes(int assignmentId)
        {
            StringBuilder result = new StringBuilder();
            List<Note> notes = (
                from note in DbQuery.Database.Notes
                where note.AssignmentID == assignmentId
                orderby note.Date descending
                select note).ToList();
            notes.ForEach(n =>
                {
                    if (result.Length > 0) result.Append("\r\n\n");
                    result.Append(string.Format("*** {0:dd/MM/yyyy HH:mm} {1} ***\r\n\n{2}", n.Date, n.User.ToString(), n.Text));
                });
            return result.ToString();
        }

        #endregion methods

        #region extension methods

        public static bool Insert(this Assignment assignment)
        {
            Logger.Debug("Insert new assignment. start");
            if (assignment.ID > 0) return false;  //assignment already in the DB

            /* this solves a linq exception:
               If an instance of the assignments budget number is loaded to the Assignment and already exists in the DB,
               Linq will throw an exception when trying to insert the new assignment.
               this function clears the instance and leaves only the budget number's ID to identify it. */
            assignment.ClearBudget();

            if (!DbQuery.Insert<Assignment>(assignment))
            {
                Logger.Error("Error on insert, check system log");
                return false;
            }
            Logger.Info("Inserted new assignment, ID = {0}", assignment.ID);
            return true;
        }

        public static Budget SetBudgetNumber(this Assignment assignment, string budgetID, int budgetTypeId)
        {
            AssignedBudget assigned = EBudget.AssignBudget(assignment.UserID, budgetID, budgetTypeId);
            assignment.Budget = assigned.Budget;
            return assigned.Budget;
        }

        public static float GetLengthInSemester(this Assignment assignment, Semester semester)
        {
            DateTime startDate = (assignment.StartDate > semester.StartDate) ?
                assignment.StartDate : semester.StartDate;
            DateTime endDate = (assignment.EndDate < semester.EndDate) ?
                assignment.EndDate : semester.EndDate;
            return (endDate - startDate).Days / 30;
        }

        private static bool DoUpdate(this Assignment assignment)
        {
            assignment.ClearBudget();
            if (!DbQuery.Update<Assignment>(assignment, GetAssignmentById(assignment.ID)))
            {
                Logger.Error("Error on DoUpdate, check system log");
                return false;
            }
            return true;
        }

        public static bool CheckIfCanUpdate(this Assignment assignment)
        {
            return (assignment.StatusID != (int)DbConstants.Status.Finished);
        }

        public static bool UpdateStatus(this Assignment assignment, DbConstants.Status newStatus)
        {
            DbConstants.Status currentStatus = (DbConstants.Status)assignment.StatusID;
            if (newStatus == currentStatus) return true;
            bool isValidStatus = true;
            switch (newStatus)
            {
                case DbConstants.Status.Approved:
                    if (currentStatus != DbConstants.Status.Pending) isValidStatus = false;
                    break;
                case DbConstants.Status.Active:
                    if (currentStatus != DbConstants.Status.Approved) isValidStatus = false;
                    break;
                case DbConstants.Status.Rejected:
                    if (currentStatus != DbConstants.Status.Pending) isValidStatus = false;
                    break;
                case DbConstants.Status.Finished:
                    break;
                case DbConstants.Status.Pending: //assignment is set to pending only on creation
                    isValidStatus = false;
                    break;
            }

            if (!isValidStatus) throw new ValidationErrorException(string.Format("UpdateStatus: can't update from {0} to {1}",
                currentStatus.ToString(), newStatus.ToString()));

            Assignment prevAssignment = null;
            assignment.StatusID = (int)newStatus;
            if (newStatus == DbConstants.Status.Active)
            {
                assignment.RealStartDate = DateTime.Now;
                if (assignment.PrevAssignmentID != null)
                {
                    prevAssignment = DbQuery.SelectByPK<Assignment>(assignment.PrevAssignmentID.ToString());
                    prevAssignment.StatusID = (int)DbConstants.Status.Finished;
                    prevAssignment.RealEndDate = DateTime.Now;
                }
            }

            if (!DbQuery.Update<Assignment>(assignment, GetAssignmentById(assignment.ID)) ||
                (prevAssignment != null && !DbQuery.Update<Assignment>(prevAssignment, GetAssignmentById(prevAssignment.ID))))
                return false;
            return true;
        }

        public static void ProduceForm(this Assignment assignment)
        {
            if (assignment.StatusID == (int)DbConstants.Status.Approved)
            {
                assignment.UpdateStatus(DbConstants.Status.Active);
            }

            if (assignment.PrevAssignmentID != null) 
                ProduceForms.ProducePrintExtension(assignment);
            else
                ProduceForms.ProduceMosadRequest(assignment);
        }

        public static decimal CurrentSalary(this Assignment assignment)
        {
            return (assignment.Salary > 0) ? assignment.Salary : EBaseSalary.CurrentBaseSalary.Salary;
        }

        public static decimal GetSalary(this Assignment assignment)
        {
            decimal salary = (assignment.Salary == 0) ? EBaseSalary.CurrentBaseSalary.Salary : assignment.Salary;
            return salary;
        }

        public static string SalaryToString(this Assignment assignment)
        {
            return decimal.Round(assignment.GetSalary(), 2).ToString();
        }

        #endregion
    }
}
